using System.Collections.Generic;
using System.Text;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * An instruction that processes the nested block within a macro instruction.
     * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
     */
    internal sealed class BodyInstruction : TemplateElement
    {
        private readonly List<Expression> _bodyParameters;

        internal BodyInstruction(List<Expression> bodyParameters)
        {
            _bodyParameters = bodyParameters;
        }

        private List<Expression> GetBodyParameters()
        {
            return _bodyParameters;
        }

        /**
         * There is actually a subtle but essential point in the code below.
         * A macro operates in the context in which it is defined. However, 
         * a nested block within a macro instruction is defined in the 
         * context in which the macro was invoked. So, we actually need to
         * temporarily switch the namespace and macro context back to
         * what it was before macro invocation to implement this properly.
         * I (JR) realized this thanks to some incisive comments from Daniel Dekany.
         */
        internal override void Accept(Environment env)
        {
            var bodyContext = new Context(this, env);
            env.Visit(bodyContext);
        }

        public override string GetCanonicalForm()
        {
            var buf = new StringBuilder("<#nested");
            if (_bodyParameters != null)
            {
                foreach (Expression bodyParameter in _bodyParameters)
                {
                    buf.Append(' ');
                    buf.Append(bodyParameter);
                }
            }
            buf.Append('>');
            return buf.ToString();
        }

        public override string GetDescription()
        {
            return "nested macro content";
        }

        /*
        boolean heedsOpeningWhitespace() {
            return true;
        }

        boolean heedsTrailingWhitespace() {
            return true;
        }
        */

        internal class Context : ILocalContext
        {
            private readonly Macro.Context _invokingMacroContext;
            private readonly Environment.Namespace _bodyVars;

            public Context(BodyInstruction bodyInstruction, Environment env)
            {
                _invokingMacroContext = env.GetCurrentMacroContext();
                List<string> bodyParameterNames = _invokingMacroContext.BodyParameterNames;
                if (bodyInstruction._bodyParameters != null)
                {
                    for (int i = 0; i < bodyInstruction._bodyParameters.Count; i++)
                    {
                        Expression exp = bodyInstruction._bodyParameters[i];
                        ITemplateModel tm = exp.GetAsTemplateModel(env);
                        if (bodyParameterNames != null && i < bodyParameterNames.Count)
                        {
                            string bodyParameterName = bodyParameterNames[i];
                            if (_bodyVars == null)
                            {
                                _bodyVars = new Environment.Namespace(env);
                            }
                            _bodyVars.Put(bodyParameterName, tm);
                        }
                    }
                }
            }

            public ITemplateModel GetLocalVariable(string name)
            {
                return _bodyVars == null ? null : _bodyVars.Get(name);
            }

            public IEnumerable<string> GetLocalVariableNames()
            {
                List<string> bodyParameterNames = _invokingMacroContext.BodyParameterNames;
                return bodyParameterNames ?? new List<string>(0);
            }
        }
    }
}